/*
 * MainFrame.java
 *
 * Created on December 13, 2009, 2:18 PM
 */

package itemrecovertool;

import com.eekboom.utils.Strings;
import itemrecovertool.tasks.RecoverTaskCell;
import itemrecovertool.tasks.RecoverTaskGroup;
import java.awt.Color;
import java.awt.Component;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.util.prefs.Preferences;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 *
 * @author  Matt
 */
public class MainFrame extends javax.swing.JFrame {
    
    ArrayList<Item> items;
    boolean searchPaths = false;
    boolean sortItemList = false;
    boolean loadItemListOnStart = false;
    
    static Preferences prefs = Preferences.systemNodeForPackage(MainFrame.class);
    private OptionsDialog optionsDialog = null;
    private JDialog itemSearchDialog = null;
    private ItemSearch itemSearch = null;
    
    private DefaultListModel model = new DefaultListModel();
    private RecoverTaskGroup recoverGroup;
    
    private static HashMap<String, Icon> icons = new HashMap<String, Icon>();
    
    public static boolean putIcon(String name, String path) {
        URL u = MainFrame.class.getResource(path);
        if (u!=null) {
            try { icons.put(name, new javax.swing.ImageIcon(u)); }
            catch (Exception e) {}
            return true;
        } else {
            return false;
        }
    }
    
    public static Icon getIcon(String name) {
        return icons.get(name);
    }
    
    static {
        putIcon("help", "/res/help.png");
        putIcon("recover", "/res/recover.png");
        putIcon("destroy", "/res/destroy.png");
        putIcon("search", "/res/search.png");
        putIcon("torchlight", "/res/torchlight2.png");
        putIcon("quest", "/res/warp.png");
        putIcon("options", "/res/options.png");
        putIcon("listremove", "/res/listremove.png");
    }
    
    protected static String defaultDataPath = "%appdata%\\runic games\\torchlight";
    protected static String defaultExePath = "C:\\Program Files\\Runic Games\\Torchlight\\Torchlight.exe";
    protected boolean usingDefaultDataPath = true;
    protected String pathData;
    protected String pathExe;
    
    protected static final String[] commonItemTypes = {"Any Item", "Axe", 
            "Belt", "Boots", "Bow", "Chest Armor", "Crossbow", "Fish", "Gloves", 
            "Helmet", "Ingredient", "Mace", "Map", "Necklace", "Pistol", 
            "Polearm", "Potion", "QuestItem", "Rifle", "Ring", "Scroll", "Shield", 
            "Shoulder Armor", "Socketable", "Spell", "Staff", "Sword", "Wand"};
    
    protected static final Comparator<Item> ITEM_COMPARATOR = new Comparator<Item>() {
        public int compare(Item o1, Item o2) {
            return Strings.compareNatural(o1.toString(), o2.toString());
        }
    };
    
    public static boolean deleteDirectory(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i=0; i<children.length; i++) {
                boolean success = deleteDirectory(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
    
        // The directory is now empty so delete it
        return dir.delete();
    }
    
    public static void copyDirectory(File sourceLocation, 
                        File targetLocation) throws IOException {
        if (sourceLocation.isDirectory()) {
            if (!targetLocation.exists()) {
                targetLocation.mkdir();
            }

            String[] children = sourceLocation.list();
            for (int i = 0; i < children.length; i++) {
                copyDirectory(new File(sourceLocation, children[i]),
                        new File(targetLocation, children[i]));
            }
        } else {
            InputStream in = new FileInputStream(sourceLocation);
            OutputStream out = new FileOutputStream(targetLocation);

            // Copy the bits from instream to outstream
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
        }
    }
    
    /** Creates new form MainFrame */
    public MainFrame() {
        initComponents();
        taskList.setModel(model);
        taskList.setCellRenderer(new ItemCellRenderer());
        taskList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);;
        
        //set up task groups to handle installing/uninstalling of mods
        recoverGroup = new RecoverTaskGroup(this);
        
        boolean firstRun = prefs.getBoolean("firstRun", true);
        
        sortItemList = prefs.getBoolean("sortItemList", false);
        searchPaths = prefs.getBoolean("searchPaths", false);
        loadItemListOnStart = prefs.getBoolean("loadItemListOnStart", false);
        
        pathData = prefs.get("pathData", null);
        usingDefaultDataPath = prefs.getBoolean("usingDefaultDataPath", true);
        
        pathExe = prefs.get("pathExe", null);
        if (pathExe==null || pathExe.length()==0)
            pathExe = getDefaultExePath();
        
        setLocationRelativeTo(null);
        setVisible(true);
        if (firstRun || pathData==null || pathExe==null) {
            prefs.putBoolean("firstRun", false);
            getOptionsDialog().showOptions(true);
        }
        
        if (runButton.isEnabled() && (pathExe==null || !new File(pathExe).exists()))
            runButton.setEnabled(false);
        
        if (loadItemListOnStart)
            getItemSearch();
    }
    
    public String getDataPath() {
        return pathData;
    }
    
    protected void setRunButtonEnabled(boolean b) {
        runButton.setEnabled(b);
    }
    
    private JDialog getItemSearchDialog() {
        if (itemSearchDialog==null) {
            populateItemList();
            itemSearchDialog = new JDialog(this, true);
            itemSearchDialog.setTitle("Select an item");
            itemSearch = new ItemSearch(this, itemSearchDialog);
            itemSearchDialog.getContentPane().add(itemSearch);
            itemSearchDialog.pack();
            itemSearchDialog.setLocationRelativeTo(null);
        }
        return itemSearchDialog;
    }
    
    private ItemSearch getItemSearch() {
        if (itemSearch==null) {
            getItemSearchDialog();
        }
        return itemSearch;
    }
    
    private OptionsDialog getOptionsDialog() {
        return (optionsDialog==null) 
                ? (optionsDialog=new OptionsDialog(this)) : optionsDialog;
    }
    
    protected String getDefaultDataPath() {
        //Tries to use the default data path, returns null if it can't be found
        String appdata = System.getenv("APPDATA");
        if (appdata!=null) {
            String dir = appdata+"\\runic games\\torchlight";
            File f = new File(dir);
            if (!f.exists()) {
                return null;
            }
            return dir;
        } else
            return null;
    }
    
    protected String getDefaultExePath() {
        String path = defaultExePath;
        File f = new File(path);
        return f.exists() ? path : null;
    }
    
    protected void cleanup(boolean quit) {
        prefs.putBoolean("sortItemList", sortItemList);
        prefs.putBoolean("searchPaths", searchPaths);
        prefs.putBoolean("loadItemListOnStart", loadItemListOnStart);
        prefs.putBoolean("usingDefaultDataPath", this.usingDefaultDataPath);
        if (pathData!=null) prefs.put("pathData", this.pathData);
        if (pathExe!=null) prefs.put("pathExe", this.pathExe);
        if (quit) System.exit(0);
    }
    
    protected void cleanup() {
        cleanup(true);
    }
    
    protected boolean populateItemList() {
        if (pathData==null)
            return false;
        try {
            File f = new File(pathData+"\\masterresourceunits.dat");
            if (!f.exists()) {
                showError("Can't find masterresourceunits.dat -- run Torchlight, and then try again.");
                return false;
            }
            
            FileInputStream fin = new FileInputStream(f);
            InputStreamReader in = new InputStreamReader(fin, "UTF16");
            BufferedReader br = new BufferedReader(in);
            String line = "";
            
            items = new ArrayList<Item>(1000);
            
            final String PRENAME = "<STRING>NAME:";
            final String PREDISPLAYNAME = "<TRANSLATE>DISPLAYNAME:";
            final String PREUNITTYPE = "<STRING>UNITTYPE:";
            final String PREPATH = "<STRING>DATFILE:";
            final String PRESET = "<STRING>SET:";
            
            String name = null;
            String displayname = null;
            String path = null;
            String unittype = null;
            String set = null;
            while ( (line=br.readLine()) != null ) {
                line = line.trim();
                if (line.equals("[MONSTERS]")) {
                    break;
                } else if (line.equals("[ITEMS]")) {
                    name = null;
                    displayname = null;
                    path = null;
                    unittype = null;
                    set = null;
                } else if (line.equals("[/ITEMS]")) {
                    if (displayname == null)
                        displayname = name;
                    if (unittype!=null)
                        items.add(new Item(name, displayname, unittype, path, set));
                } else if (line.startsWith(PRENAME)) {
                    if (name==null)
                        name = line.substring(PRENAME.length());
                } else if (line.startsWith(PRESET)) {
                    set = line.substring(PRESET.length());
                } else if (line.startsWith(PREDISPLAYNAME)) {
                    displayname = line.substring(PREDISPLAYNAME.length());
                } else if (line.startsWith(PREUNITTYPE)) {
                    unittype = line.substring(PREUNITTYPE.length());
                } else if (line.startsWith(PREPATH)) {
                    path = line.substring(PREPATH.length());
                }
            }
            br.close();
            in.close();
            if (sortItemList)
                sortItemList();
            return true;
        } catch (Exception e) {
            showError("Could not load item list - "+e.getClass().getName()
                    +" - "+e.getMessage(), "Error");
            e.printStackTrace();
            return false;
        }
    }
    
    /** Sorts the item list if it exists. */
    protected void sortItemList() {
        if (items!=null)
            Collections.sort(items, ITEM_COMPARATOR);
    }
    
    private boolean itemIsAHit(Item i, String queryPart, boolean searchPath) {
        if (queryPart.trim().length()==0)
            return true;
        if (i.DISPLAYNAME.toLowerCase().contains(queryPart)
                || i.NAME.toLowerCase().contains(queryPart))
            return true;
        if (searchPath && i.PATH.toLowerCase().contains(queryPart))
            return true;
        return false;
    }
    
    protected Vector<Item> search(String query, String typeFilter, boolean setsOnly) {
        if (items==null)
            return new Vector<Item>();
        query = query.toLowerCase().trim();
        String[] queryParts = query.split(" ");
        if (typeFilter!=null)
            typeFilter = typeFilter.toUpperCase();
        if (typeFilter.equals("ANY ITEM"))
            typeFilter = null;
        
        Vector<Item> v = new Vector<Item>();
        for (int x=0; x<items.size(); x++) {
            Item i = items.get(x);
            //only search within proper types
            
            //If type filter is null, search within
            //If type filter matches UNITTYPE, search within
            //and...
            //If setsOnly==true, SET must be non-null
            if ((typeFilter==null || i.UNITTYPE.equals(typeFilter))
                    && (!setsOnly || i.SET!=null) ) {
                if (query.length()==0) {
                    v.addElement(i);
                } else {
                    boolean good = false;
                    for (int j=0; j<queryParts.length; j++) {
                        if (itemIsAHit(i, queryParts[j], searchPaths))
                            good = true;
                        else {
                            good = false;
                            break;
                        }
                    }
                    if (good)
                        v.addElement(i);
                }
            }
        }
        return v;
    }
    
    
    public void showError(String msg) {
        showError(msg, "Error");
    }
    
    public void showError(String msg, String title) {
        showMessage(msg, title, JOptionPane.ERROR_MESSAGE);
    }
    
    public void showWarning(String msg) {
        showWarning(msg, "Warning");
    }
    
    public void showWarning(String msg, String title) {
        showMessage(msg, title, JOptionPane.WARNING_MESSAGE);
    }
    
    public void showMessage(String msg, String title, int type) {
        JOptionPane.showMessageDialog(this, msg, title, type);
    }
    
    
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        toolbar = new javax.swing.JToolBar();
        addItem = new javax.swing.JButton();
        deleteItem = new javax.swing.JButton();
        warpTask = new javax.swing.JButton();
        options = new javax.swing.JButton();
        jButton1 = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        taskList = new javax.swing.JList();
        jPanel1 = new javax.swing.JPanel();
        installBtn = new javax.swing.JButton();
        runButton = new javax.swing.JButton();
        uninstallBtn = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Torchlight Bugfix");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        toolbar.setFloatable(false);
        toolbar.setRollover(true);

        addItem.setIcon(getIcon("recover"));
        addItem.setToolTipText("New Task: Recover Item");
        addItem.setFocusable(false);
        addItem.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        addItem.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        addItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addItemActionPerformed(evt);
            }
        });
        toolbar.add(addItem);

        deleteItem.setIcon(getIcon("destroy"));
        deleteItem.setToolTipText("New Task: Destroy Item");
        deleteItem.setFocusable(false);
        deleteItem.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        deleteItem.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        toolbar.add(deleteItem);

        warpTask.setIcon(getIcon("quest"));
        warpTask.setToolTipText("New Task: Fix Quest");
        warpTask.setFocusable(false);
        warpTask.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        warpTask.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        toolbar.add(warpTask);

        options.setIcon(getIcon("options"));
        options.setToolTipText("Open options dialog");
        options.setFocusable(false);
        options.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        options.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        options.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                optionsActionPerformed(evt);
            }
        });
        toolbar.add(Box.createHorizontalGlue());
        toolbar.add(options);

        jButton1.setIcon(getIcon("help"));
        jButton1.setToolTipText("Show Help");
        jButton1.setFocusable(false);
        jButton1.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jButton1.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        toolbar.add(jButton1);

        jScrollPane1.setViewportView(taskList);

        installBtn.setFont(installBtn.getFont().deriveFont(installBtn.getFont().getStyle() | java.awt.Font.BOLD));
        installBtn.setForeground(new java.awt.Color(0, 153, 0));
        installBtn.setText("Install");
        installBtn.setToolTipText("Installs the above tasks");
        installBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                installBtnActionPerformed(evt);
            }
        });
        jPanel1.add(installBtn);

        runButton.setIcon(getIcon("torchlight"));
        runButton.setToolTipText("Run Torchlight.exe");
        runButton.setPreferredSize(new java.awt.Dimension(60, 50));
        runButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                runButtonActionPerformed(evt);
            }
        });
        jPanel1.add(runButton);

        uninstallBtn.setFont(uninstallBtn.getFont().deriveFont(uninstallBtn.getFont().getStyle() | java.awt.Font.BOLD));
        uninstallBtn.setForeground(new java.awt.Color(204, 0, 0));
        uninstallBtn.setText("Uninstall");
        uninstallBtn.setToolTipText("Uninstalls all tasks created by this tool");
        uninstallBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                uninstallBtnActionPerformed(evt);
            }
        });
        jPanel1.add(uninstallBtn);

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 278, Short.MAX_VALUE)
            .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, jScrollPane1)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, toolbar, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 258, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(toolbar, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 288, Short.MAX_VALUE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jPanel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void addItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addItemActionPerformed
        getItemSearch().newSearch();
        Item item = getItemSearch().getSelectedItem();
        if (item!=null) {
            //add it to the group so it can be installed
            recoverGroup.addItem(item);
            
            //add it to the list using a custom-rendered cell
            TaskCell c = new RecoverTaskCell(item);
            model.addElement(c);
        }
}//GEN-LAST:event_addItemActionPerformed

    private void optionsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_optionsActionPerformed
        getOptionsDialog().showOptions();
    }//GEN-LAST:event_optionsActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        cleanup(false);
    }//GEN-LAST:event_formWindowClosing
    
    private boolean anyTasksChanged() {
        if (model.isEmpty())
            return false;
        if (recoverGroup.changed())
            return true;
        return false;
    }
    
    private void runButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_runButtonActionPerformed
        if (pathExe!=null) {
            //TODO: "Not all tasks have been installed. Run anyways?"
            if (anyTasksChanged()) {
                int c = JOptionPane.showConfirmDialog(this, "Not all tasks have " +
                        "been installed. Run anyways?", "Warning", JOptionPane.YES_NO_OPTION,
                        JOptionPane.WARNING_MESSAGE);
                if (c!=JOptionPane.YES_OPTION)
                    return;
            }
            try {
                Process p = Runtime.getRuntime().exec("\""+pathExe+"\"");
                cleanup();
            } catch (Exception e) {
                setState(NORMAL);
                e.printStackTrace();
                showError("Could not run Torchlight.exe -- "+e.getMessage());
            }
        }
    }//GEN-LAST:event_runButtonActionPerformed

    private void installBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_installBtnActionPerformed
        if (model.size()==0) {
            showError("No tasks to install!");
            return;
        }
        boolean success = false;
        
        //installs all tasks
        success = recoverGroup.install();
        if (success)
            showMessage("Tasks installed successfully.", "Success", 
                    JOptionPane.INFORMATION_MESSAGE);
    }//GEN-LAST:event_installBtnActionPerformed

    private void uninstallBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uninstallBtnActionPerformed
        boolean success = false;
        
        //uninstalls all tasks
        success = recoverGroup.uninstall();
        model.clear();
        recoverGroup.clear();
        if (success)
            showMessage("Tasks uninstalled successfully.", "Success", 
                    JOptionPane.INFORMATION_MESSAGE);
    }//GEN-LAST:event_uninstallBtnActionPerformed
    
    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }
        catch (Exception e) {}
        
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new MainFrame();
            }
        });
    }
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addItem;
    private javax.swing.JButton deleteItem;
    private javax.swing.JButton installBtn;
    private javax.swing.JButton jButton1;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JButton options;
    private javax.swing.JButton runButton;
    private javax.swing.JList taskList;
    private javax.swing.JToolBar toolbar;
    private javax.swing.JButton uninstallBtn;
    private javax.swing.JButton warpTask;
    // End of variables declaration//GEN-END:variables

    class ItemCellRenderer extends JLabel implements ListCellRenderer {
        
        public ItemCellRenderer() {
            setOpaque(true);
            setVerticalAlignment(JLabel.CENTER);
            setVerticalTextPosition(JLabel.CENTER);
            setHorizontalTextPosition(JLabel.TRAILING);
        }

        public Component getListCellRendererComponent(
                       JList list,
                       Object value,
                       int index,
                       boolean isSelected,
                       boolean cellHasFocus) {
            TaskCell t = ((TaskCell)value);
            setText(t.toString());
            setToolTipText(t.getToolTipText());
            setIcon(t.getIcon());
            
            if (isSelected) {
                setBackground(list.getSelectionBackground());
                setForeground(list.getSelectionForeground());
            } else {
                setBackground(list.getBackground());
                setForeground(list.getForeground());
            }
            return this;
        }
    }
}

